<template>
  <div class="file-preview-page">
    <div class="fp-header">
      <button class="fp-back" @click="goBack">返回</button>
      <div class="fp-title">{{ displayTitle }}</div>
      <button v-if="canDownload" class="fp-download" @click="doDownload">下载</button>
      <!-- 保留：在原生模式下可切换为 PDF.js 兼容渲染 -->
      <button v-if="previewType === 'pdf' && canDownload" class="fp-download" @click="usePdfJs">兼容渲染</button>
      
      <!-- 删除：不保留切换原生按钮 -->
      
    </div>

    <!-- 将 loading/error 改为 v-show，避免销毁预览容器，确保 DOCX 容器一直挂载 -->
    <div v-show="loading" class="fp-loading">正在加载...</div>
    <div v-show="!loading && error" class="fp-error">
      <p>{{ error }}</p>
      <button class="fp-retry" @click="reload">重试</button>
    </div>

    <!-- 始终渲染预览容器，类型通过 v-show 控制可见性 -->
    <div class="fp-body">
      <!-- 图片 -->
      <img v-show="previewType === 'image'" :src="blobUrl" class="fp-img" alt="图片预览" />

      <!-- PDF：使用 iframe 简单内嵌；若在部分移动端不支持，将提示“在系统浏览器打开”或下载 -->
      <iframe v-show="previewType === 'pdf'" :src="blobUrl" class="fp-iframe" title="PDF预览"></iframe>

      <!-- 新增：PDF.js 兜底渲染容器 -->
      <div v-show="previewType === 'pdfjs'" ref="pdfjsContainer" class="fp-pdfjs"></div>

      <!-- DOCX：使用 docx-preview 渲染（使用内层 pan 包裹，解决缩放裁剪与横向滚动问题） -->
      <div v-show="previewType === 'docx'" ref="docxContainer" class="fp-docx">
        <div ref="docxPan" class="fp-docx-pan"></div>
      </div>

      <!-- 文本 -->
      <pre v-show="previewType === 'text'" class="fp-text">{{ textContent }}</pre>

      <!-- 其他类型：给出外部打开/下载选项 -->
      <div v-show="previewType === 'unknown'" class="fp-unknown">
        <p>暂不支持该文件类型的在线预览。</p>
        <div class="fp-actions">
          <button class="fp-open" @click="openExternal">在系统浏览器打开</button>
          <button class="fp-download" @click="doDownload">下载</button>
        </div>
      </div>
    </div>
    <!-- 钉钉式缩放工具条：适配宽度 / 缩小 / 放大 -->
    <!-- <div v-show="previewType === 'docx'" class="fp-toolbar">
      <button class="fp-tool-btn" @click="fitToWidth">适配宽度</button>
      <button class="fp-tool-btn" @click="applyScale(scale - 0.15)">缩小</button>
      <button class="fp-tool-btn" @click="applyScale(scale + 0.15)">放大</button>
    </div> -->
  </div>
</template>

<script setup>
import { ref, computed, onMounted, nextTick, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { request, API_BASE_URL } from '@/utils/http'
import { refreshTokenApi } from '@/api/index'
import { storage } from '@/utils/storage'
import { renderAsync } from 'docx-preview'
// 新增：PDF.js 相关导入（ESM + Vite）
import { GlobalWorkerOptions, getDocument } from 'pdfjs-dist/build/pdf'
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.min.js?url'
GlobalWorkerOptions.workerSrc = pdfWorkerSrc

const route = useRoute()
const router = useRouter()

const docxContainer = ref(null)
const docxPan = ref(null)
const pdfjsContainer = ref(null) // 新增：PDF.js 容器引用
// 组件销毁标记，用于在返回/卸载后终止异步渲染与定时任务，避免空容器引用
let disposed = false

const loading = ref(true)
const error = ref('')
const blobUrl = ref('')
const blobObj = ref(null)
const textContent = ref('')
const previewType = ref('unknown') // 'image' | 'pdf' | 'pdfjs' | 'docx' | 'text' | 'unknown'

const rawPath = computed(() => {
  const q = route.query?.path
  try {
    const p = typeof q === 'string' ? decodeURIComponent(q) : (Array.isArray(q) ? decodeURIComponent(q[0]) : '')
    return (p || '').replace(/\\/g, '/').trim()
  } catch {
    return ''
  }
})
const rawName = computed(() => {
  const q = route.query?.name
  try {
    return typeof q === 'string' ? decodeURIComponent(q) : (Array.isArray(q) ? decodeURIComponent(q[0]) : '')
  } catch {
    return ''
  }
})
const displayTitle = computed(() => rawName.value || '文件预览')
const canDownload = computed(() => !!blobObj.value)

// 动态调整 meta viewport：在预览页允许缩放，离开时恢复
let originalViewportContent = ''
const setViewport = (content) => {
  const meta = document.querySelector('meta[name="viewport"]')
  if (meta && content) meta.setAttribute('content', content)
}
const enablePreviewViewport = () => {
  const meta = document.querySelector('meta[name="viewport"]')
  originalViewportContent = meta?.getAttribute('content') || ''
  setViewport('width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes')
}
const restoreViewport = () => {
  if (originalViewportContent) setViewport(originalViewportContent)
}

// 钉钉式缩放：对 docx 根节点做 transform 缩放，支持双指缩放
const scale = ref(1)
const minScale = 0.5
const maxScale = 3
let docRootEl = null
let listenersAttached = false
let docNaturalWidth = 0
let docNaturalHeight = 0

// 修复“二次缩放”导致内容变窄：始终以文档自然尺寸为基础，只用 transform 控制缩放
const applyScale = (s, opts = {}) => {
  const { clamp = true, scrollToLeft = false, center = false, containerWidthPx } = opts
  const target = clamp ? Math.max(minScale, Math.min(maxScale, s)) : Math.min(maxScale, s)
  scale.value = target
  const panEl = docxPan.value
  if (panEl) {
    const scaledWidth = docNaturalWidth ? docNaturalWidth * target : panEl.offsetWidth
    const offsetX = (center && typeof containerWidthPx === 'number' && typeof scaledWidth === 'number')
      ? Math.max(0, Math.floor((containerWidthPx - scaledWidth) / 2))
      : 0
    panEl.style.transform = `translateX(${offsetX}px) scale(${target})`
    panEl.style.transformOrigin = 'top left'
    if (docNaturalWidth) {
      panEl.style.width = `${docNaturalWidth}px`
    }
    if (docNaturalHeight) {
      panEl.style.height = `${docNaturalHeight}px`
    }
    panEl.style.marginLeft = ''
    panEl.style.marginRight = ''
  }
  if (scrollToLeft && docxContainer.value) {
    docxContainer.value.scrollLeft = 0
  }
}

// 以容器（手机屏宽）为基准适配，去掉强制设置 pan 宽度，避免与缩放叠加
const fitToWidth = () => {
  const pan = docxPan.value
  docRootEl = pan?.querySelector('.docx') || pan?.firstElementChild
  if (!pan || !docRootEl || !docxContainer.value) return
  const firstPage = docRootEl.querySelector?.('.docx-page')
  const docW = docNaturalWidth || firstPage?.offsetWidth || firstPage?.clientWidth || docRootEl?.scrollWidth || docRootEl?.clientWidth || pan?.scrollWidth || pan?.clientWidth || 0
  const docH = docNaturalHeight || docRootEl?.scrollHeight || docRootEl?.clientHeight || pan?.scrollHeight || pan?.clientHeight || 0
  docNaturalWidth = docW
  docNaturalHeight = docH
  const containerRect = docxContainer.value.getBoundingClientRect?.()
  const containerW = (containerRect?.width || docxContainer.value.clientWidth) || 0
  if (containerW && docNaturalWidth) {
    const safeW = Math.max(0, Math.floor(containerW))
    const ratio = safeW / docNaturalWidth
    applyScale(ratio, { clamp: false, center: true, containerWidthPx: safeW, scrollToLeft: true })
  } else {
    applyScale(1)
  }
}

function getDist(touches) {
  const [a, b] = touches
  const dx = a.pageX - b.pageX
  const dy = a.pageY - b.pageY
  return Math.hypot(dx, dy)
}
let pinchStartDist = 0
let pinchStartScale = 1
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent || '')
const onTouchStart = (e) => {
  if (e.touches?.length === 2) {
    pinchStartDist = getDist(e.touches)
    pinchStartScale = scale.value
  }
}
const onTouchMove = (e) => {
  if (e.touches?.length === 2) {
    e.preventDefault()
    const dist = getDist(e.touches)
    const ratio = dist / (pinchStartDist || dist)
    applyScale(pinchStartScale * ratio)
  }
}
const onTouchEnd = () => {}
// iOS WKWebView/Safari 非标准手势事件兜底
const onGestureStart = (e) => {
  pinchStartScale = scale.value
}
const onGestureChange = (e) => {
  e.preventDefault?.()
  const gestureScale = e.scale || 1
  applyScale(pinchStartScale * gestureScale)
}
const onGestureEnd = () => {}
const attachGestureListeners = () => {
  if (listenersAttached || !docxContainer.value) return
  // 多指缩放（标准）
  docxContainer.value.addEventListener('touchstart', onTouchStart, { passive: false })
  docxContainer.value.addEventListener('touchmove', onTouchMove, { passive: false })
  docxContainer.value.addEventListener('touchend', onTouchEnd, { passive: true })
  // iOS 兜底
  if (isIOS) {
    docxContainer.value.addEventListener('gesturestart', onGestureStart, { passive: false })
    docxContainer.value.addEventListener('gesturechange', onGestureChange, { passive: false })
    docxContainer.value.addEventListener('gestureend', onGestureEnd, { passive: true })
  }
  listenersAttached = true
}

const guessTypeByExt = (name = '') => {
  const lower = name.toLowerCase()
  const ext = lower.split('.').pop()
  if (!ext) return 'unknown'
  if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) return 'image'
  if (ext === 'pdf') return 'pdf'
  if (ext === 'docx') return 'docx'
  if (['txt', 'log', 'json', 'md'].includes(ext)) return 'text'
  return 'unknown'
}

const fetchBlobWithRefresh = async (filePath) => {
  const makeRequest = async () => {
    const response = await request(`${API_BASE_URL}/OA/file/download`, {
      method: 'GET',
      params: { path: filePath },
      responseType: 'blob',
    })
    return response
  }
  const resp = await makeRequest()
  const headers = resp?.headers || {}
  const contentType = headers['content-type'] || headers['Content-Type'] || ''
  const rawBlob = (resp && resp.data) ? resp.data : resp
  const isJson = String(contentType).toLowerCase().includes('application/json')
  if (isJson) {
    try {
      const text = await rawBlob.text()
      const json = JSON.parse(text)
      const msg = json?.msg || json?.message || ''
      if (json?.code === 401 || /token/.test(msg)) {
        try {
          const refreshResp = await refreshTokenApi()
          const newToken = refreshResp?.data?.accessToken
          if (newToken) {
            storage.setItem('accessToken', newToken)
            const retried = await makeRequest()
            const retriedBlobData = (retried && retried.data) ? retried.data : retried
            return retriedBlobData instanceof Blob ? retriedBlobData : new Blob([retriedBlobData])
          }
          throw new Error('刷新token失败，请重新登录')
        } catch (e) {
          throw new Error(json?.msg || '登录已过期，请重新登录')
        }
      }
      // 非401业务JSON，返回文本Blob
      return new Blob([text], { type: 'text/plain' })
    } catch (e) {
      // 解析失败，按普通Blob处理
      return rawBlob instanceof Blob ? rawBlob : new Blob([rawBlob])
    }
  }
  return rawBlob instanceof Blob ? rawBlob : new Blob([rawBlob])
}

// 新增：外部直链抓取为 Blob（需目标站允许 CORS）
const isExternalLink = (p) => /^https?:\/\//i.test(p || '')
const fetchExternalBlob = async (url) => {
  const resp = await fetch(url, { mode: 'cors' })
  if (!resp.ok) throw new Error('外链拉取失败')
  const type = resp.headers.get('content-type') || ''
  const blob = await resp.blob()
  if (type && blob.type !== type) {
    return new Blob([blob], { type })
  }
  return blob
}

// 新增：使用 PDF.js 渲染（将 PDF Blob 渲染为多个画布）
const renderPdfWithPdfJs = async (blob) => {
  try {
    if (disposed) return
    // 捕获当前容器引用，避免在渲染过程中组件卸载导致 ref 变为 null
    let containerEl = pdfjsContainer.value
    if (!containerEl) await nextTick()
    if (disposed) return
    containerEl = pdfjsContainer.value || containerEl
    if (!containerEl || !containerEl.appendChild) {
      // 容器未就绪（可能是组件已卸载），直接安静退出，避免报错影响返回页
      return
    }
    // 清空容器
    containerEl.innerHTML = ''
    // 计算容器宽度，按屏幕宽度自适配
    const containerRect = containerEl.getBoundingClientRect?.()
    const containerWidth = Math.floor(containerRect?.width || containerEl.clientWidth || window.innerWidth)
    const dpr = Math.max(1, Math.floor(window.devicePixelRatio || 1))

    const data = await blob.arrayBuffer()
    const loadingTask = getDocument({ data })
    const pdf = await loadingTask.promise
    for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
      if (disposed) break
      const page = await pdf.getPage(pageNum)
      // 以 scale=1 获取基础视口，再计算适配屏宽的缩放比
      const baseViewport = page.getViewport({ scale: 1 })
      const fitScale = containerWidth > 0 ? (containerWidth / baseViewport.width) : 1
      const viewport = page.getViewport({ scale: fitScale })
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      // CSS 尺寸：按容器宽度适配
      canvas.style.width = Math.floor(viewport.width) + 'px'
      canvas.style.height = Math.floor(viewport.height) + 'px'
      // 真实像素尺寸：乘以 DPR 提高清晰度
      canvas.width = Math.floor(viewport.width * dpr)
      canvas.height = Math.floor(viewport.height * dpr)
      if (!containerEl || disposed || !containerEl.appendChild) break
      containerEl.appendChild(canvas)
      await page.render({ canvasContext: ctx, viewport, transform: [dpr, 0, 0, dpr, 0, 0] }).promise
    }
  } catch (err) {
    console.error('PDF.js 渲染失败:', err)
    error.value = err?.message || 'PDF.js 渲染失败'
  }
}

// 新增：触发 PDF.js 兜底渲染
const usePdfJs = async () => {
  if (!blobObj.value) return
  previewType.value = 'pdfjs'
  await renderPdfWithPdfJs(blobObj.value)
}
// 删除：不保留切换为原生查看器
// const useNativePdf = async () => {
//   if (!blobObj.value) return
//   if (!blobUrl.value) {
//     blobUrl.value = URL.createObjectURL(blobObj.value)
//   }
//   previewType.value = 'pdf'
// }

const reload = async () => {
  loading.value = true
  error.value = ''
  blobUrl.value = ''
  blobObj.value = null
  textContent.value = ''
  previewType.value = 'unknown'
  const path = rawPath.value
  if (!path) {
    error.value = '缺少预览路径'
    loading.value = false
    return
  }
  try {
    const useExternal = isExternalLink(path)
    const blob = useExternal ? await fetchExternalBlob(path) : await fetchBlobWithRefresh(path)
    blobObj.value = blob
    // 先根据MIME判断，再根据扩展名兜底
    const type = (blob.type || '').toLowerCase()
    if (type.startsWith('image/')) {
      previewType.value = 'image'
      blobUrl.value = URL.createObjectURL(blob)
    } else if (type === 'application/pdf') {
      // 默认使用 PDF.js 
      blobUrl.value = URL.createObjectURL(blob)
      previewType.value = 'pdfjs'
      await nextTick()
      await renderPdfWithPdfJs(blob)
    } else if (type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || guessTypeByExt(rawName.value) === 'docx') {
      previewType.value = 'docx'
      // 等待容器挂载后再渲染，避免 docx-preview 内部对 null 访问报错
      await nextTick()
      if (!docxContainer.value) {
        await nextTick()
      }
      if (docxContainer.value && docxPan.value) {
        docxPan.value.innerHTML = ''
        await renderAsync(blob, docxPan.value, undefined, {
          inWrapper: true,
          className: 'docx-viewer',
          ignoreWidth: false,
          ignoreHeight: false,
          useMathMLPolyfill: true,
        })
        // 渲染完成后：测量文档自然尺寸 -> 适配宽度(不放大) -> 绑定手势缩放
        docRootEl = docxPan.value.querySelector('.docx') || docxPan.value.firstElementChild
        docNaturalWidth = Math.max(docRootEl?.scrollWidth || 0, docRootEl?.clientWidth || 0, docRootEl?.getBoundingClientRect?.().width || 0)
        docNaturalHeight = Math.max(docRootEl?.scrollHeight || 0, docRootEl?.clientHeight || 0, docRootEl?.getBoundingClientRect?.().height || 0)
       
       applyScale(1)
        fitToWidth()
        attachGestureListeners()
      } else {
        throw new Error('DOCX容器未就绪，请稍后重试')
      }
    } else if (type.startsWith('text/') || guessTypeByExt(rawName.value) === 'text') {
      previewType.value = 'text'
      textContent.value = await blob.text()
    } else {
      previewType.value = 'unknown'
      blobUrl.value = URL.createObjectURL(blob)
    }
  } catch (e) {
    const msg = e?.message || ''
    if (msg.includes('登录')) {
      error.value = msg
      const currentPath = window.location.hash.slice(1)
      setTimeout(() => {
        window.location.href = `/#/login?redirect=${encodeURIComponent(currentPath || '/')}`
      }, 1200)
    } else if (/401/.test(msg) || /token/.test(msg)) {
      error.value = '登录已过期，请重新登录'
      const currentPath = window.location.hash.slice(1)
      setTimeout(() => {
        window.location.href = `/#/login?redirect=${encodeURIComponent(currentPath || '/')}`
      }, 1200)
    } else {
      error.value = msg || '预览失败，请稍后重试'
    }
  } finally {
    loading.value = false
  }
}

const goBack = () => router.back()

const doDownload = () => {
  if (!blobObj.value) return
  const url = blobUrl.value || URL.createObjectURL(blobObj.value)
  const a = document.createElement('a')
  a.href = url
  a.download = rawName.value || '下载文件'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}

const openExternal = () => {
  // 尝试新开页；若被拦截则在当前页跳转
  const url = blobUrl.value || (blobObj.value ? URL.createObjectURL(blobObj.value) : '')
  if (!url) return
  const ua = navigator.userAgent || ''
  const inWechat = /MicroMessenger/i.test(ua)
  const isIOS = /iPhone|iPad|iPod/i.test(ua)
  const opened = window.open(url, '_blank')
  if (!opened || inWechat || isIOS) {
    setTimeout(() => {
      window.location.href = url
    }, 50)
  }
}

// 视口变化时自动重新“适配宽度”，避免横竖屏或窗口改变后出现溢出或留白
const onResize = () => {
  if (previewType.value === 'docx') {
    fitToWidth()
  }
  // 新增：PDF.js 模式下按屏宽重新渲染以适配（轻度防抖）
  if (previewType.value === 'pdfjs') {
    if (disposed || !blobObj.value) return
    if (onResize._timer) {
      clearTimeout(onResize._timer)
    }
    onResize._timer = setTimeout(async () => {
      if (disposed) return
      await renderPdfWithPdfJs(blobObj.value)
    }, 200)
  }
}

onMounted(() => {
  enablePreviewViewport()
  reload()
  window.addEventListener('resize', onResize, { passive: true })
})

onUnmounted(() => {
  disposed = true
  restoreViewport()
  window.removeEventListener('resize', onResize)
  // 清理定时器，避免卸载后继续触发渲染
  if (onResize._timer) {
    clearTimeout(onResize._timer)
    onResize._timer = null
  }
  if (docxContainer.value) {
    docxContainer.value.removeEventListener('touchstart', onTouchStart)
    docxContainer.value.removeEventListener('touchmove', onTouchMove)
    docxContainer.value.removeEventListener('touchend', onTouchEnd)
    if (isIOS) {
      docxContainer.value.removeEventListener('gesturestart', onGestureStart)
      docxContainer.value.removeEventListener('gesturechange', onGestureChange)
      docxContainer.value.removeEventListener('gestureend', onGestureEnd)
    }
  }
  listenersAttached = false
  if (pdfjsContainer.value) {
    pdfjsContainer.value.innerHTML = ''
  }
})
</script>

<style scoped>
.file-preview-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #fff;
}
.fp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border-bottom: 1px solid #f0f0f0;
}
.fp-back, .fp-download, .fp-open, .fp-retry {
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: #fff;
}
.fp-title {
  flex: 1;
  text-align: center;
  font-size: 16px;
  color: #323233;
  padding: 0 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fp-body {
  flex: 1;
  overflow: hidden;
}
.fp-loading, .fp-error {
  padding: 24px;
  text-align: center;
  color: #969799;
}
.fp-iframe {
  width: 100%;
  height: 100%; /* 使用父容器高度，避免与 body 双层滚动冲突 */
  border: none;
}
.fp-img {
  max-width: 100%;
  height: auto;
  display: block;
}
.fp-docx {
  padding: 0;
  height: 100%;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y; /* 单指滚动，双指由脚本处理缩放 */
  overscroll-behavior: contain;
  width: 100%;
}
/* 新增：pan 包裹层，解决缩放后的滚动度量与裁剪问题 */
.fp-docx-pan {
  position: relative;
  display: block;
  margin: 0; /* 使用平移控制居中，避免 margin:auto 与缩放的耦合问题 */
  transform-origin: top left;
  width: max-content;
  height: max-content;
  will-change: transform, width, height;
}
/* 让 docx-preview 输出的页面左对齐，避免默认居中导致左侧留白 */
.fp-docx :deep(.docx-wrapper) {
  padding: 0 !important;
  text-align: left !important; /* 页面左对齐，由脚本负责居中或贴边 */
}
/* 使用文档自身宽度，由缩放控制视图，避免初始被放大 */
.fp-docx :deep(.docx-page) {
  margin: 0 !important; /* 去掉页面级 margin，避免视图向左偏移 */
}
.fp-docx :deep(.docx) {
  width: auto !important;
  max-width: none !important;
  box-shadow: none !important;
  margin: 0 !important;
  will-change: transform;
}
.fp-docx :deep(img) {
  max-width: 100% !important;
  height: auto !important;
}
.fp-docx :deep(table) {
  width: 100% !important;
  max-width: 100% !important;
}
.fp-text {
  padding: 12px;
  white-space: pre-wrap;
  word-break: break-word;
}
.fp-unknown {
  padding: 24px;
  text-align: center;
}
.fp-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
/* 工具条 */
.fp-toolbar {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  padding: 6px 10px;
  border-radius: 20px;
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 1000;
}
.fp-tool-btn {
  color: #fff;
  background: transparent;
  border: none;
  padding: 6px 8px;
  font-size: 12px;
}
.fp-pdfjs {
  height: 100%;
  padding: 8px;
  box-sizing: border-box;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}
.fp-pdfjs :deep(canvas) {
  display: block;
  width: 100% !important; /* 保证按容器宽度适配 */
  height: auto !important;
}
</style>